/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package io.iec.edp.caf.context.filter;

import io.iec.edp.caf.boot.context.CAFContext;
import io.iec.edp.caf.boot.context.event.FilterInvokeEventBroker;
import io.iec.edp.caf.commons.runtime.CafEnvironment;
import io.iec.edp.caf.commons.transaction.service.DBTransactionService;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import io.iec.edp.caf.boot.context.CAFBizContextHolder;
import io.iec.edp.caf.context.entity.CommonConstant;
import io.iec.edp.caf.context.util.TraceIdGenerator;
import io.iec.edp.caf.core.context.*;
import io.iec.edp.caf.core.session.CafSessionThreadHolder;
import io.iec.edp.caf.msu.api.ServiceUnitAwareService;
import io.iec.edp.caf.msu.api.entity.ServiceUnitInfo;
import io.iec.edp.caf.rest.RESTEndpoint;
import io.iec.edp.caf.rest.RESTEndpointRegistrar;
import io.iec.edp.caf.rest.api.properties.CAFServerConfigurationProperties;

import io.iec.edp.caf.tenancy.api.TenantResolveContributor;
import io.iec.edp.caf.tenancy.api.context.RequestTenantContextHolder;
import io.iec.edp.caf.tenancy.api.context.RequestTenantContextInfo;
import io.iec.edp.caf.tenancy.core.context.MultiTenantContextHolder;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.slf4j.MDC;
import org.springframework.lang.NonNull;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.time.OffsetDateTime;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class CAFContextFilter extends OncePerRequestFilter {
    private final static String DEFAULT_BIZCONTEXT_HEADER_NAME = "X-CAF-Runtime-Context";
    private Lock lock = new ReentrantLock();
    private final BizContextManager contextManager;
    private RESTEndpointRegistrar registrar;
    private final FilterInvokeEventBroker filterInvokeEventBroker;
    private final CAFServerConfigurationProperties properties;
    private ServiceUnitAwareService suAware;
    private Map<String, String> suNames;
    //临时用
    private boolean commonSULoaded = false;

    private TenantResolveContributor tenantResolveContributor;

    //数据库未提交事务注册服务
    private DBTransactionService dbTransactionService;

    public CAFContextFilter(BizContextManager contextManager,
                            FilterInvokeEventBroker filterInvokeEventBroker,
                            CAFServerConfigurationProperties properties,
                            ServiceUnitAwareService suAware,
                            TenantResolveContributor tenantResolveContributor,
                            DBTransactionService dbTransactionService) {
        this.contextManager = contextManager;
        this.filterInvokeEventBroker = filterInvokeEventBroker;
        this.properties = properties;
        this.suAware = suAware;
        this.tenantResolveContributor = tenantResolveContributor;
        this.dbTransactionService = dbTransactionService;

        this.suNames = new HashMap<>();
        for (String name : this.suAware.getEnabledServiceUnits()) {
            suNames.put(name.toLowerCase(), name);
        }
    }

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        String path = request.getRequestURI();
        String basepath = CafEnvironment.getBaseApiPath();
//        return !path.startsWith("/api/");
        return !path.startsWith(basepath);
    }

    @Override
    protected void doFilterInternal(
            @NonNull HttpServletRequest request,
            @NonNull HttpServletResponse response,
            @NonNull FilterChain filterChain
    ) throws IOException, ServletException {

        String contextId = request.getHeader(DEFAULT_BIZCONTEXT_HEADER_NAME) == null
                ? request.getHeader("SessionId")
                : request.getHeader(DEFAULT_BIZCONTEXT_HEADER_NAME);

        Integer tenantId = tenantResolveContributor.resolveTenantId(request);
        if (tenantId != null) {
            RequestTenantContextHolder.set(new RequestTenantContextInfo(tenantId));
        }
        if (TransactionSynchronizationManager.isActualTransactionActive()) {
            //手动关闭事务管理器
            TransactionSynchronizationManager.clear();
            throw new ServletException("系统检测到存在未提交的事务，请联系管理员检查！");
        }

        //返回站点的根路径
//        String contextPath = request.getContextPath();
        //包含根路径的URI
        String path = request.getRequestURI();
        try {
            //服务单元处理
            String suName = this.getServiceUnitName(path);
            var service = CAFContext.current.getService();
            if (service != null)
                ((CafContextServiceImpl) service).setCurrentSU(suName);

            if (properties.isLazyInitialization()) {
                String pathInfo = request.getPathInfo() == null ? "" : request.getPathInfo();
                RESTEndpointRegistrar registrar = this.getRESTEndpointRegistrar();
                if (!registrar.isRegistered(pathInfo)) {
                    RESTEndpoint endpoint = registrar.findRESTEndpoint(suName, pathInfo);
                    if (endpoint != null && !registrar.isRegistered(endpoint.getPath())) {
                        registrar.register(endpoint, pathInfo);
                    } else {
                        //临时增加commonSU的处理
                        if (!commonSULoaded) {
                            try {
                                lock.lock();
                                if (!commonSULoaded) {
                                    List<ServiceUnitInfo> infos = this.suAware.getAllServiceUnits();
                                    for (ServiceUnitInfo info : infos) {
                                        if (info.getType() != null && info.getType().equalsIgnoreCase("Common") && this.suNames.containsKey(info.getName().toLowerCase())) {
                                            this.registrar.getRestEndPoints(info.getName());
                                        }
                                    }
                                    commonSULoaded = true;
                                    endpoint = registrar.findRESTEndpoint(suName, pathInfo);
                                    if (endpoint != null && !registrar.isRegistered(endpoint.getPath())) {
                                        registrar.register(endpoint, pathInfo);
                                    }
                                }
                            } finally {
                                lock.unlock();
                            }
                        }
                    }

                }
            }
            //BizContext处理
            Optional<BizContext> context = CAFBizContextHolder.getCurrentContext();
            if (!context.isPresent() && !StringUtils.isEmpty(contextId)) {
                BizContext currentBizContext = this.getBizContext(contextId);
                if (currentBizContext != null)
                    CAFBizContextHolder.setCurrentContext(currentBizContext);
            }

            //设置语言后缀
            this.setLanguageFieldSuffix();

            //设置日志traceId
            this.initTraceVariable(request);

            this.filterInvokeEventBroker.fireBeforeFilterInvokeEvent(request, response);

        } finally {
            filterChain.doFilter(request, response);

            //记录未提交DB事务
            if (TransactionSynchronizationManager.isActualTransactionActive()) {
                logger.error("请求结束时存在未提交的事务，当前请求: " + path);

                //getIPv4() 取服务器name耗时久 调整为异步
                OffsetDateTime now = OffsetDateTime.now();
                new Thread(() -> {
                    dbTransactionService.save(path, "请求结束时存在未提交的事务!服务器名称和IP地址为: " + getIPv4(), now, "Request");
                }).start();
            }

            this.filterInvokeEventBroker.fireAfterFilterInvokeEvent(request, response);
            CAFBizContextHolder.purge();
            CafSessionThreadHolder.purge();
            MultiTenantContextHolder.purge();
            RequestTenantContextHolder.purge();
            CAFContext.current.getService().setCurrentSU(null);
        }
    }

    private RESTEndpointRegistrar getRESTEndpointRegistrar() {
        if (this.registrar == null) {
            this.registrar = SpringBeanUtils.getBean(RESTEndpointRegistrar.class);
        }
        return this.registrar;
    }

    private String getServiceUnitName(String url) {
        // /api/gsp/communication/v1.0/test/test
        Integer index = url.indexOf("/api/");
        if (index < 0)
            return null;

        String subStr = url.substring(index + 5);
        if (subStr == null || "".equals(subStr))
            return null;
        String[] strArray = subStr.split("/");
        String suName = strArray[1];

        String suNameLowerCase = suName.toLowerCase();
//        var list = GspConfigurationPathService.Current.ConfigPathList;
//        //暂时忽略大小写，1909再按照规范
//        return list.Keys.ToList().Find(x => x.Equals(suName, StringComparison.OrdinalIgnoreCase));
        if (this.suNames.containsKey(suNameLowerCase))
            return this.suNames.get(suNameLowerCase);

        return suName;
    }

    /**
     * 根据ContextId获取上下文并设置线程
     *
     * @param contextId
     * @return
     */
    private BizContext getBizContext(String contextId) {
        try {
            return contextManager.fetch(contextId);
        } catch (BizContextNotFoundException e) {
            log.info(e.getMessage());
            return null;
        }
    }

    /**
     * 设置语言后缀
     */
    private void setLanguageFieldSuffix() {
        if (CAFContext.current.getCurrentSession() == null)
            return;
        String language = CAFContext.current.getLanguage();
        CafContextWrapper.current.setCurrentLanguage(language);
    }

    /**
     * 初始跟踪
     */
    private void initTraceVariable(HttpServletRequest request) {
        String traceId = request.getHeader(CommonConstant.TRACE_ID_HEADER);
        if (StringUtils.isEmpty(traceId)) {
            //生成一个管理起来
            traceId = TraceIdGenerator.generate();
            MDC.put(CommonConstant.LOG_TRACE_ID, traceId);
        }

        String msu = CAFContext.current.getCurrentSU();
        if (!StringUtils.isEmpty(msu)) {
            MDC.put(CommonConstant.LOG_MSU_ID, msu);
        }
    }

    /**
     * 返回当前服务器的ipv4地址列表(不包含回环地址)
     */
    private List<String> getIPv4() {
        List<String> ips = null;
        boolean acquired = false;
        try {
            ips = new ArrayList<>();
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            while (interfaces.hasMoreElements()) {
                NetworkInterface networkInterface = interfaces.nextElement();
                Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    InetAddress address = addresses.nextElement();
                    //ipv4 && 非本地回环地址
                    if (address instanceof Inet4Address && !address.isLoopbackAddress()) {
                        if (!acquired) {
                            //getHostName耗时严重，此方法需异步调用
                            ips.add(address.getHostName());

                            acquired = true;
                        }

                        ips.add(address.getHostAddress());
                    }
                }
            }
        } catch (Exception e) {
            logger.error("未提交事务获取服务器IPv4地址异常", e);
        }
        return ips;
    }

}
